package org.jproggy.pagesally;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.jproggy.snippetory.Encodings;
import org.jproggy.snippetory.Template;
import org.jproggy.snippetory.TemplateContext;
import org.jproggy.snippetory.spi.Configurer;


public class Generator implements Configurer {
	private static final Logger log = Logger.getLogger(Generator.class);
	private final File baseDir;
	private final File targetDir;
	private Template frame;
	private final String frameFileName = "frame";
	private final Encodings targetEncoding = Encodings.html;
	private final String pageSuffix = ".page";
	private final Locale locale;
	
	/**
	 *   
	 */
	public static void main(String[] args) throws IOException {
		BasicConfigurator.configure();
		if (args.length != 3) {
			System.out.println("Usage: <from> <to> <locale>");
			return;
		}
		File from = new File(args[0]);
		File to = new File(args[1]);
		Locale l = new Locale(args[2]);
		new Generator(from, to, l).generate(from);
	}
	
	public Generator(File baseDir, File targetDir, Locale locale) throws IOException {
		this.baseDir = baseDir;
		this.targetDir = targetDir;
		this.locale = locale;
		loadFrame();
	}

	private void loadFrame() throws FileNotFoundException {
		File frameFile = new File(baseDir, frameFileName + '.' + targetEncoding.getName());
		if (!frameFile.exists()) {
			throw new FileNotFoundException(frameFile.getAbsolutePath());
		}
		frame = context().parseFile(frameFile);
	}

	private TemplateContext context() {
		return new TemplateContext().encoding(targetEncoding).locale(locale);
	}
	
	private Map<String, File> fileResolver;
	private synchronized Map<String, File> getFileResolver() {
		if (fileResolver == null) {
			fileResolver = new HashMap<String, File>();
			addPages(baseDir);
		}
		return fileResolver;
	}
	
	private void addPages(File dir) {
		for (File page: pages(dir)) {
			fileResolver.put(toPath(page), page);
		}
		for (File subDir: subDirs(dir)) {
			addPages(subDir);
		}
	}

	public boolean hasPage(String targetPath) {
		return getFileResolver().containsKey(targetPath);
	}

	public void generateSingleFile(String targetPath, Writer out) throws IOException {
		File source = getFileResolver().get(targetPath);
		File navFile = new File(source.getParentFile(), "menu." + targetEncoding.getName());
		Template navTpl = navFile.exists() ? context().parseFile(navFile) : null;
		generatePage(source, navTpl, out);
		
	}
	
	public void generate(File dir) throws IOException {
		File navFile = new File(dir, "menu." + targetEncoding.getName());
		Template navTpl = navFile.exists() ?  context().parseFile(navFile) : null;
		for (File page: pages(dir)) {
			generatePage(page, navTpl, target(page));
		}
		for (File subDir: subDirs(dir)) {
			generate(subDir);
		}
	}
	
	private void generatePage(File pageSource, Template navTpl, Writer out) throws IOException {
		loadFrame();
		frame.clear();
		navTpl.clear();
		log.info("Gernerating page " + pageSource.getName());
		/*		
		Fragment fragm = new Fragment(null, frame);
		if (navTpl != null) {
			navTpl.clear();
			fragm = new Fragment(fragm, navTpl);
		}
		fragm = new Fragment(fragm, context().parseFile(pageSource));
		fragm.resolve().render(out);
*/
		Set<String> frameNames = frame.names();
		Template page = context().parseFile(pageSource);
		handleRegions(frameNames, navTpl, frame);
		bind(frameNames, navTpl);
		Set<String> bound = new HashSet<String>();
		handleRegions(frameNames, page, frame);
		bound.addAll(frameNames);
		handleRegions(navTpl.names(), page, navTpl);
		bound.addAll(navTpl.names());
		bind(bound, page);
		
		page.render(navTpl, "content");
		navTpl.render(frame, "menu");
		frame.render(out);
	}

	private void handleRegions(Set<String> frameNames, Template spec, Template base) {
		for (String region: spec.regionNames()) {
			if (frameNames.contains(region)) {
				spec.get(region).render(base, region);
			}
		}
	}

	private void bind(Set<String> bound, Template page) {
		for (String region: page.regionNames()) {
			if (!bound.contains(region)) {
				page.get(region).render();
			}
		}
	}

	private Writer target(File pageSource) throws IOException {
		String relName = toPath(pageSource);
		File targetFile = new File(targetDir, relName);
		targetFile.getParentFile().mkdirs();
		return new OutputStreamWriter(new FileOutputStream(targetFile), "utf-8");
	}

	private String toPath(File pageSource) {
		String relName = baseDir.toURI().relativize(pageSource.toURI()).toString();
		relName = relName.substring(0, relName.length() - (pageSuffix.length())) + '.' + targetEncoding.getName();
		return '/' + relName;
	}

	private File[] subDirs(File dir) {
		return dir.listFiles(SUB_DIRS);
	}
	
	private File[] pages(File dir) {
		return dir.listFiles(pages);
	}
	
	private final static FileFilter SUB_DIRS = new FileFilter() {
		@Override
		public boolean accept(File pathname) {
			if (!pathname.isDirectory()) return false;
			if (pathname.getName().startsWith(".")) return false;
			return true;
		}
	};
	
	private final FileFilter pages = new FileFilter() {
		@Override
		public boolean accept(File pathname) {
			if (!pathname.isFile()) return false;
			if (!pathname.getName().endsWith(pageSuffix)) return false;
			return true;
		}
	};
}
